/* scroller.c:
 * -----------
 *
 * A scrolling buffer utility.  Able to add and subtract to the buffer.
 * All routines that would require a screen update will automatically refresh
 * the scroller.
 */

/* Local Includes */
#if HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

/* System Includes */
#if HAVE_CTYPE_H
#include <ctype.h>
#endif

#if HAVE_STDLIB_H 
#include <stdlib.h>
#endif  /* HAVE_STDLIB_H */

#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */

/* Local Includes */
#include "cgdb.h"
#include "scroller.h"
#include "highlight.h"
#include "highlight_groups.h"

/* --------------- */
/* Local Functions */
/* --------------- */

/* count: Count the occurrences of a character c in a string s.
 * ------
 *
 *   s:  String to search
 *   c:  Character to search for
 *
 * Return Value:  Number of occurrences of c in s.
 */
static int count(const char *s, char c)
{
	int rv = 0;
	char *x = strchr(s, c);

	while (x){
		rv++;
		x = strchr(x+1, c);
	}

	return rv;
}

/* parse: Translates special characters in a string.  (i.e. backspace, tab...)
 * ------
 *
 *   buf:  The string to parse
 *
 * Return Value:  A newly allocated copy of buf, with modifications made.
 */
static char *parse(struct scroller *scr, const char *orig, const char *buf)
{
	const int tab_size = 8;
	int length = strlen(orig) + strlen(buf) + (tab_size-1) * count(buf, '\t');
	char *rv = (char *) malloc(length + 1);
	int i, j;

	/* Zero out the string */
	memset(rv, 0, length+1);
	strcpy(rv, orig);
	i = scr->current.pos;

	/* Expand special characters */
	for (j = 0; j < strlen(buf); j++){
		switch (buf[j]){
			/* Backspace/Delete -> Erase last character */
			case 8:
			case 127:
				if (i > 0)
					i--;
				break;
				/* Tab -> Translating to spaces */
			case '\t':
				do
					rv[i++] = ' ';
				while (i % tab_size != 0);
				break;
				/* Carriage return -> Move back to the beginning of the line */
			case '\r':
				i = 0;
				break;
				/* Default case -> Only keep printable characters */
			default:
				if (isprint((int)buf[j])){
					rv[i] = buf[j];
					i++;
				}
				break;
		}
	}

	scr->current.pos = i;
	/* Remove trailing space from the line */
	for (j = strlen(rv)-1; j > i && isspace((int)rv[j]); j--);
	rv[j+1] = 0;

	return realloc(rv, strlen(rv)+1);
}

/* ----------------- */
/* Exposed Functions */
/* ----------------- */

/* See scroller.h for function descriptions. */

struct scroller *scr_new(int pos_r, int pos_c, int height, int width)
{
	struct scroller *rv;

	if ((rv = malloc(sizeof(struct scroller))) == NULL)
		return NULL;

	rv->current.r   = 0;
	rv->current.c   = 0;
	rv->current.pos = 0;
	rv->win         = newwin(height, width, pos_r, pos_c);

	/* Start with a single (blank) line */
	rv->buffer      = malloc(sizeof(char *));
	rv->buffer[0]   = strdup("");
	rv->length      = 1;
	rv->updated = true;
	return rv;
}

void scr_free(struct scroller *scr)
{
	int i;

	/* Release the buffer */
	if (scr->length){
		for (i = 0; i < scr->length; i++)
			free(scr->buffer[i]);
		free(scr->buffer);
	}
	delwin(scr->win);

	/* Release the scroller object */
	free(scr);
}

void scr_clear(struct scroller *scr)
{
	int i;
	/* Release the buffer */
	if (scr->length){
		for (i = 0; i < scr->length; i++)
			free(scr->buffer[i]);
		free(scr->buffer);
	}
	scr->current.r   = 0;
	scr->current.c   = 0;
	scr->current.pos = 0;

	/* Start with a single (blank) line */
	scr->buffer      = malloc(sizeof(char *));
	scr->buffer[0]   = strdup("");
	scr->length      = 1;
//	scr->updated = true;
}


void scr_upEx(struct scroller *scr, int nlines)
{
	if (scr->buffer)
	{
		scr->current.r -= nlines;

		if (scr->current.r < 0)
			scr->current.r= 0;
		/* The display message and status bar takes a line */
		if (scr->current.r >= scr->length - 1)
			scr->current.r = scr->length - 2;
	}
//	scr->updated = true;
}

void scr_downEx(struct scroller *scr, int nlines)
{
	if (scr->buffer)
	{
		scr->current.r += nlines;

		if (scr->current.r < 0)
			scr->current.r= 0;
		/* The display message and status bar takes a line */
		if (scr->current.r >= scr->length - 1)
			scr->current.r = scr->length - 2;
	}
//	scr->updated = true;
}
void scr_up(struct scroller *scr, int nlines)
{
	int height, width;
	int length;
	int i;

	/* Sanity check */
	getmaxyx(scr->win, height, width);
	if (scr->current.c > 0){
		if (scr->current.c % width != 0)
			scr->current.c = (scr->current.c / width) * width;
	}

	for (i = 0; i < nlines; i++){
		/* If current column is positive, drop it by 'width' */
		if (scr->current.c > 0)
			scr->current.c -= width;

		/* Else, decrease the current row number, and set column accordingly */
		else{
			if (scr->current.r > 0){
				scr->current.r--;
				if ((length = strlen(scr->buffer[scr->current.r])) > width)
					scr->current.c = ((length-1) / width) * width;
			}
			else{
				/* At top */
				break;
			}
		}
	}
//	scr->updated = true;
}

void scr_down(struct scroller *scr, int nlines)
{
	int height, width;
	int length;
	int i;

	/* Sanity check */
	getmaxyx(scr->win, height, width);
	if (scr->current.c > 0){
		if (scr->current.c % width != 0)
			scr->current.c = (scr->current.c / width) * width;
	}

	for (i = 0; i < nlines; i++){
		/* If the current line wraps to the next, then advance column number */
		length = strlen(scr->buffer[scr->current.r]);
		if (scr->current.c < length - width)
			scr->current.c += width;

		/* Otherwise, advance row number, and set column number to 0. */
		else{
			if (scr->current.r < scr->length-1){
				scr->current.r++;
				scr->current.c = 0;
			}
			else{
				/* At bottom */
				break;
			}
		}
	}
//	scr->updated = true;
}
/*
   void scr_left(struct scroller *scr)
   {
   int height, width;
   int length;
   int i;
   getmaxyx(scr->win, height, width);
   scr->current.r = 0;
   scr->current.c = 0;
   }
   void scr_right(struct scroller *scr)
   {
   int height, width;
   int length;
   int i;
   getmaxyx(scr->win, height, width);
   scr->current.r = 0;
   scr->current.c = 0;
   }
   */
void scr_home(struct scroller *scr)
{
	scr->current.r = 0;
	scr->current.c = 0;
//	scr->updated = true;
}

void scr_end(struct scroller *scr)
{
	int height, width;

	getmaxyx(scr->win, height, width);

	scr->current.r = scr->length - 1;
	scr->current.c = (strlen(scr->buffer[scr->current.r]) / width) * width;
//	scr->updated = true;
}

void scr_addEx(struct scroller *scr, const char *buf)
{
	int distance;               /* Distance to next new line character */
	int length;                 /* Length of the current line */ 
	char *x;                    /* Pointer to next new line character */

	/* Find next newline in the string */
	x        = strchr(buf, '\n');
	length   = strlen(scr->buffer[scr->length-1]);
	distance = x ? x-buf : strlen(buf);

	/* Append to the last line in the buffer */
	if (distance > 0){
		char *temp = scr->buffer[scr->length-1];
		char *buf2 = malloc(distance + 1);
		strncpy(buf2, buf, distance);
		buf2[distance] = 0;
		scr->buffer[scr->length-1] = parse(scr, temp, buf2);
		if(scr->buffer[scr->length-1][0] == '=' && scr->buffer[scr->length-1][1] == '>')
			scr->current.r = scr->length-1;
		free(temp);
		free(buf2);
	}

	/* Create additional lines if buf contains newlines */
	while (x != NULL){
		char *newbuf;
		buf      = x + 1;
		x        = strchr(buf, '\n');
		distance = x ? x-buf : strlen(buf);

		/* Create a new buffer that stops at the next newline */
		newbuf = malloc(distance+1);
		memset(newbuf, 0, distance+1);
		strncpy(newbuf, buf, distance);

		/* Expand the buffer */
		scr->length++;
		scr->buffer = realloc(scr->buffer, sizeof(char *)*scr->length);
		scr->current.pos = 0;

		/* Add the new line */
		scr->buffer[scr->length-1] = parse(scr, "", newbuf);
		if(scr->buffer[scr->length-1][0] == '=' && scr->buffer[scr->length-1][1] == '>')
			scr->current.r = scr->length-1;
		free(newbuf);
	}
//	scr->updated = true;
}
void scr_add(struct scroller *scr, const char *buf)
{
	int distance;               /* Distance to next new line character */
	int length;                 /* Length of the current line */ 
	char *x;                    /* Pointer to next new line character */

	/* Find next newline in the string */
	x        = strchr(buf, '\n');
	length   = strlen(scr->buffer[scr->length-1]);
	distance = x ? x-buf : strlen(buf);

	/* Append to the last line in the buffer */
	if (distance > 0){
		char *temp = scr->buffer[scr->length-1];
		char *buf2 = malloc(distance + 1);
		strncpy(buf2, buf, distance);
		buf2[distance] = 0;
		scr->buffer[scr->length-1] = parse(scr, temp, buf2);
		free(temp);
		free(buf2);
	}

	/* Create additional lines if buf contains newlines */
	while (x != NULL){
		char *newbuf;
		buf      = x + 1;
		x        = strchr(buf, '\n');
		distance = x ? x-buf : strlen(buf);

		/* Create a new buffer that stops at the next newline */
		newbuf = malloc(distance+1);
		memset(newbuf, 0, distance+1);
		strncpy(newbuf, buf, distance);

		/* Expand the buffer */
		scr->length++;
		scr->buffer = realloc(scr->buffer, sizeof(char *)*scr->length);
		scr->current.pos = 0;

		/* Add the new line */
		scr->buffer[scr->length-1] = parse(scr, "", newbuf);
		free(newbuf);
	}

	scr_end(scr);
//	scr->updated = true;
}

void scr_move(struct scroller *scr, 
		int pos_r, int pos_c, int height, int width)
{
	delwin(scr->win);
	scr->win = newwin(height, width, pos_r, pos_c);
	wclear(scr->win);
//	scr->updated = true;
} 

void scr_refresh(struct scroller *scr, int focus)
{
	int length;                          /* Length of current line */
	int nlines;                          /* Number of lines written so far */
	int r;                               /* Current row in scroller */
	int c;                               /* Current column in row */
	int width, height;                   /* Width and height of window */
	char *buffer;                        /* Current line segment to print */

//	if(scr->updated != true)
//		return;
//	scr->updated = false;
	/* Sanity check */
	getmaxyx(scr->win, height, width);

	if (scr->current.c > 0){
		if (scr->current.c % width != 0)
			scr->current.c = (scr->current.c / width) * width;
	}
	r = scr->current.r;
	c = scr->current.c;
	buffer = malloc(width + 1);
	buffer[width] = 0;

	/* Start drawing at the bottom of the viewable space, and work our way up */
	for (nlines = 1; nlines <= height; nlines++){

		/* Print the current line [segment] */        
		memset(buffer, ' ', width);
		if (r >= 0){
			length = strlen(scr->buffer[r]+c);
			memcpy(buffer, scr->buffer[r]+c, length < width ? length : width);
		}
		mvwprintw(scr->win, height-nlines, 0, "%s", buffer);

		/* Update our position */
		if (c >= width)
			c -= width;
		else{
			r--;
			if (r >= 0){
				length = strlen(scr->buffer[r]);
				if (length > width)
					c = ((length-1)/width) * width;
			}
		} 
	}

	length = strlen(scr->buffer[scr->current.r]+scr->current.c);
	if (focus && scr->current.r == scr->length - 1 && length <= width){
		/* We're on the last line, draw the cursor */
		curs_set(1);
		wmove(scr->win, height - 1, scr->current.pos % width);
	} else {
		/* Hide the cursor */
		curs_set(0);
	}

	free(buffer);
	wrefresh(scr->win);
}


/* print_in_middle: Prints the message 'string' centered at line in win 
 * ----------------
 *
 *  win:    Curses window
 *  line:   The line to print the message at
 *  width:  The width of the window
 *  string: The message to print
 */
static void print_in_middle(WINDOW *win, int line, int width, char *string) {  
	int  x, y;
	int j;
	int length = strlen(string);

	getyx(win, y, x);

	x = (int)((width-length)/2);

	wmove(win, line, 0);
	for (j = 0; j < x; j++)
		waddch(win, ' ');

	mvwprintw(win, line, x, "%s", string);

	for (j = x + length; j < width; j++)
		waddch(win, ' ');
}


int scr_display(struct scroller *scr, bool is_in_focus)
{
	char fmt[5];
	int width, height;
	int lwidth;
	int line;
	int i;
	int attr;
	static char *label = "Disassembly.";

//	if(scr->updated != true)
//		return 0;

	curs_set(0);

	/* Check that a line is loaded */
	if (scr == NULL || scr->buffer == NULL){
		wrefresh(scr->win);
		return 0;
	}



	/* Initialize variables */
	getmaxyx(scr->win, height, width);

	/* The status bar and display line 
	 * Fake the display function to think the height is 2 lines less */
	height-=2;

	// Set starting line number (center source line if it's small enough)
	if (scr->length < height)
		line = 0;
	else{
		line = scr->current.r - height / 2;
		if (line > scr->length - height)
			line = scr->length - height;
		else if (line < 0)
			line = 0;
	}

	//line = 0;
	/* Print 'height' lines of the line, starting at 'line' */
	lwidth = (int) log10(scr->length)+1;
	sprintf(fmt, "%s", "%d");
	print_in_middle(scr->win, 0, width, label);
	wmove(scr->win, 1, 0);

	for (i = 0; i < width; i++)
		waddch(scr->win, ' ');

	for (i = 2; i < height + 1; i++, line++){
		wmove(scr->win, i, 0);
		if (has_colors()){
			/* Outside of linename, just finish drawing the vertical line */
			if (line < 0 || line >= scr->length-1){
				int j;
				for (j = 1; j < lwidth; j++)
					waddch(scr->win, ' ');
				waddch(scr->win, '~');
				wattron(scr->win, A_BOLD);
				waddch(scr->win, VERT_LINE);
				wattroff(scr->win, A_BOLD);
				for (j = 2+lwidth; j < width; j++)
					waddch(scr->win, ' ');
			}
			/* Mark the current line with an arrow */
			else if (line == scr->current.r){
				waddch(scr->win, ' ');
				wattron(scr->win, A_BOLD);
				wattroff(scr->win, A_BOLD);
				if (hl_groups_get_attr (hl_groups_instance, HLG_ARROW, &attr) == -1)
					return -1;
				if(is_in_focus)
					wattron(scr->win, attr);
				waddch(scr->win, '|');
				waddch(scr->win, ' ');
				hl_wprintw(scr->win, scr->buffer[line], width-lwidth-2, scr->current.c);
				if(is_in_focus)
					wattroff(scr->win, attr);
			}
			/* Ordinary line */
			else{
				waddch(scr->win, ' ');
				wattron(scr->win, A_BOLD);
				waddch(scr->win, VERT_LINE);
				wattroff(scr->win, A_BOLD);
				waddch(scr->win, ' ');

				/* No special line information */
				hl_wprintw(scr->win, scr->buffer[line], width-lwidth-2, scr->current.c);
			}
		}
		else{
			wprintw(scr->win, " %s\n", scr->buffer[line]);
		}
	}

	//wmove(scr->win, height - (line - scr->buf->sel_line) - 1, lwidth+2);
	wrefresh(scr->win);

	return 0;
}

